# External WebGL Contexts with MapLibre

This guide shows how to connect luma.gl to a WebGL context that is created and owned by [MapLibre GL JS](https://maplibre.org/projects/maplibre-gl-js/). It uses the `webgl2Adapter.attach` API to wrap the map's context in a `WebGLDevice` and keep a `WebGLCanvasContext` synchronized with MapLibre's canvas.

## Install dependencies

Add MapLibre GL JS alongside luma.gl:

```bash
npm install @luma.gl/webgl @luma.gl/engine maplibre-gl
```

This example uses [CARTO basemaps](https://github.com/CartoDB/basemap-styles) so you do not need an API token, but you can still supply a Mapbox token if you point MapLibre at a Mapbox-hosted style elsewhere in your application.

## Attach luma.gl to MapLibre

Create the map first so MapLibre owns the WebGL canvas, then attach the device to that context:

```typescript
import maplibregl from 'maplibre-gl'
import {Matrix4} from '@math.gl/core'
import {webgl2Adapter} from '@luma.gl/webgl'
import {Model} from '@luma.gl/engine'

const map = new maplibregl.Map({
  container: 'map',
  style: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
  antialias: true,
  pitch: 60,
  zoom: 12
})

map.on('load', async () => {
  const webglContext = map.getCanvas().getContext('webgl2') as WebGL2RenderingContext
  const device = await webgl2Adapter.attach(webglContext, {createCanvasContext: {autoResize: false}})

  // Keep the WebGLCanvasContext aligned with MapLibre's drawing buffer
  device.canvasContext.resize({width: webglContext.drawingBufferWidth, height: webglContext.drawingBufferHeight})

  const modelMatrix = new Matrix4()
  const viewProjection = new Matrix4()

  const overlay = new Model(device, {
    id: 'maplibre-overlay',
    vs: `...`,
    fs: `...`,
    shaderLayout: {
      attributes: [
        {name: 'positions', location: 0, format: 'float32x3'}
      ],
      bindings: [{name: 'app', type: 'uniform', location: 0}]
    },
    attributes: {
      positions: new Float32Array([...])
    },
    vertexCount: 6,
    bindings: {
      app: /* uniform buffer */
    }
  })

  map.addLayer({
    id: 'luma-gl-overlay',
    type: 'custom',
    renderingMode: '3d',
    render: (_, matrix) => {
      viewProjection.fromArray(matrix as number[])
      // Update uniforms and draw without clearing the map's buffers
      const renderPass = device.beginRenderPass({clearColor: false, clearDepth: false})
      overlay.draw(renderPass)
      renderPass.end()
      map.triggerRepaint()
    }
  })
})
```

## Handle map resizes

Because the context comes from MapLibre, luma.gl cannot resize it automatically. Listen for MapLibre `resize` events and keep the `WebGLCanvasContext` in sync:

```typescript
map.on('resize', () => {
  const webglContext = map.getCanvas().getContext('webgl2') as WebGL2RenderingContext
  device.canvasContext.resize({
    width: webglContext.drawingBufferWidth,
    height: webglContext.drawingBufferHeight
  })
})
```

## See it in action

The revived [External WebGL Context example](/examples/api/external-webgl-context) renders a luma.gl overlay driven by MapLibre's WebGL render loop and uses the `webgl2Adapter.attach` API to keep both frameworks in sync.
